﻿// <copyright Autor="twitter.com/gotactile">
// Distributed under Microsoft Public License (Ms-PL)
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Diagnostics;
using System.Windows.Media.Animation;
using System.Runtime.InteropServices;
using System.Windows.Threading;

namespace Con10uum
{
    /// <summary>
    /// State machine to manage gesture for all different state of Con10uum 
    /// </summary>
    internal class Con10uumGestureStateManager
    {
        private Con10uum m_Con10uum;
        private Con10uumStateBase m_Con10uumCurrentState;
        private readonly NormalState m_Con10uumNormalState;
        private readonly ZoomingState m_Con10uumZoomingState;
        private readonly ZoomedState m_Con10uumZoomedState;

        internal Con10uumGestureStateManager(Con10uum con10uum)
        {
            m_Con10uum = con10uum;

            m_Con10uum.IsManipulationEnabled = true;

            m_Con10uumNormalState = new NormalState(this);
            m_Con10uumZoomingState = new ZoomingState(this);
            m_Con10uumZoomedState = new ZoomedState(this);

            m_Con10uumCurrentState = m_Con10uumNormalState;

            m_Con10uum.Loaded += new RoutedEventHandler(m_Con10uum_Loaded);
        }

        internal Con10uum Con10uum { get { return m_Con10uum; } }

        internal Con10uumStateBase Con10uumCurrentState { get { return m_Con10uumCurrentState; } }

        private void m_Con10uum_Loaded(object sender, RoutedEventArgs e)
        {
            m_Con10uum.PreviewTouchDown += new EventHandler<TouchEventArgs>(m_Con10uum_PreviewTouchDown);
            m_Con10uum.PreviewTouchMove += new EventHandler<TouchEventArgs>(m_Con10uum_PreviewTouchMove);
            m_Con10uum.PreviewTouchUp += new EventHandler<TouchEventArgs>(m_Con10uum_PreviewTouchUp);

            m_Con10uum.ManipulationStarting += new EventHandler<ManipulationStartingEventArgs>(this_ManipulationStarting);
            m_Con10uum.ManipulationDelta += new EventHandler<ManipulationDeltaEventArgs>(this_ManipulationDelta);
            m_Con10uum.ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(this_ManipulationCompleted);
            m_Con10uum.ManipulationBoundaryFeedback += new EventHandler<ManipulationBoundaryFeedbackEventArgs>(this_ManipulationBoundaryFeedback);
        }

        void m_Con10uum_PreviewTouchDown(object sender, TouchEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
                m_Con10uumCurrentState.PreviewTouchDown(sender, e);
            }
        }

        void m_Con10uum_PreviewTouchMove(object sender, TouchEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
                m_Con10uumCurrentState.PreviewTouchMove(sender, e);
            }
        }

        void m_Con10uum_PreviewTouchUp(object sender, TouchEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
                m_Con10uumCurrentState.PreviewTouchUp(sender, e);
            }
        }

        private void this_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
                try
                {
                    m_Con10uumCurrentState.ManipulationStarting(sender, e);
                }
                catch{}
            }
        }

        private void this_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
               try
               {
                   m_Con10uumCurrentState.ManipulationDelta(sender, e);
               }
               catch{}
            }
        }

        private void this_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
                try
                {
                    m_Con10uumCurrentState.ManipulationCompleted(sender, e);
                }
                catch{}
            } 
        }

        private void this_ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
        {
            if (m_Con10uumCurrentState != null)
            {
                m_Con10uumCurrentState.ManipulationBoundaryFeedback(sender, e);
            }
        }

        internal void GotoZoomingState()
        {
            m_Con10uumCurrentState.ExitingState();
            m_Con10uumZoomingState.EnteringState();
            m_Con10uumCurrentState = m_Con10uumZoomingState;
        }

        internal void GotoZoomedState()
        {
            m_Con10uumCurrentState.ExitingState();
            m_Con10uumZoomedState.EnteringState();
            m_Con10uumCurrentState = m_Con10uumZoomedState;
        }

        internal void GotoNormalState()
        {
            m_Con10uumCurrentState.ExitingState();
            m_Con10uumNormalState.EnteringState();
            m_Con10uumCurrentState = m_Con10uumNormalState;
        }
    }

    internal abstract class Con10uumStateBase
    {
        internal abstract void EnteringState();

        internal abstract void ExitingState();

        internal abstract void ManipulationStarting(object sender, ManipulationStartingEventArgs e);

        internal abstract void ManipulationDelta(object sender, ManipulationDeltaEventArgs e);

        internal abstract void ManipulationCompleted(object sender, ManipulationCompletedEventArgs e);

        internal abstract void ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e);

        internal virtual void PreviewTouchDown(object sender, TouchEventArgs e)
        {
            
        }

        internal virtual void PreviewTouchUp(object sender, TouchEventArgs e)
        {
            
        }

        internal virtual void PreviewTouchMove(object sender, TouchEventArgs e)
        {
            
        }
    }

    internal class NormalState : Con10uumStateBase
    {
        private Con10uumGestureStateManager m_Con10uumGestureStateManager;
        private Con10uum m_Con10uum;
        private Con10uumPanel m_Con10uumPanel;

        internal NormalState(Con10uumGestureStateManager con10uumMultitouchStateMachine)
        {
            m_Con10uumGestureStateManager = con10uumMultitouchStateMachine;
            m_Con10uum = m_Con10uumGestureStateManager.Con10uum;
            m_Con10uum.Loaded += new RoutedEventHandler(m_Con10uum_Loaded); ;
        }

        void m_Con10uum_Loaded(object sender, RoutedEventArgs e)
        {
            m_Con10uumPanel = (Con10uumPanel)m_Con10uum.Template.FindName("Con10uumPanel", m_Con10uum);
        }

        internal override void EnteringState()
        {
        }

        internal override void ExitingState()
        {

        }

        internal override void ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            e.ManipulationContainer = m_Con10uum;
            e.Mode = ManipulationModes.All;
            e.Handled = true;
        }

        internal override void ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            try
            {
                var touchcount = m_Con10uum.TouchesOver.Count();

                var deltaManipulation = e.DeltaManipulation;
                var cumulativeManipulation = e.CumulativeManipulation;

                if (touchcount > 0)
                {
                    //scroll gesture
                    m_Con10uumPanel.SetHorizontalOffset(m_Con10uumPanel.HorizontalOffset - deltaManipulation.Translation.X);
                }

                if (touchcount > 3)
                {
                    //Check if we need to start to manage Zooming Gesture (Aka : ZoomOut gesture)
                    //Debug.WriteLine("ScaleY {0}, X {1}", cumulativeManipulation.Scale.Y, Math.Abs(cumulativeManipulation.Translation.X));
                    if (cumulativeManipulation.Scale.Y < 0.85 && Math.Abs(cumulativeManipulation.Translation.X) < 40)
                    {
                        m_Con10uumGestureStateManager.GotoZoomingState();
                    }
                }

                e.Handled = true;
            }
            catch
            {
            }
        }

        internal override void ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
        }

        internal override void ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
        {
            System.Diagnostics.Debug.WriteLine("Mani boundary " + e.BoundaryFeedback.Translation.ToString());
        }

        //internal override void PreviewTouchDown(object sender, TouchEventArgs e)
        //{
        //    base.PreviewTouchDown(sender, e);

        //    var touchcount = m_Con10uum.TouchesOver.Count();

        //    if (touchcount > 3)
        //    {
        //        //scroll gesture
        //        e.Handled = true;
        //    }

        //}

        //internal override void PreviewTouchMove(object sender, TouchEventArgs e)
        //{
        //    base.PreviewTouchMove(sender, e);

        //    var touchcount = m_Con10uum.TouchesOver.Count();

        //    if (touchcount > 3)
        //    {
        //        //scroll gesture
        //        e.Handled = true;
        //    }

        //}

        //internal override void PreviewTouchUp(object sender, TouchEventArgs e)
        //{
        //    base.PreviewTouchUp(sender, e);

        //    var touchcount = m_Con10uum.TouchesOver.Count();

        //    if (touchcount > 3)
        //    {
        //        //scroll gesture
        //        e.Handled = true;
        //    }

        //}
    }

    internal class ZoomingState : Con10uumStateBase
    {
        private Con10uumGestureStateManager m_Con10uumGestureStateManager;
        private Con10uum m_Con10uum;
        private Con10uumPanel m_Con10uumPanel;
        private Storyboard m_StoryboardScaleTo03;

        private double m_ScaleGestureTrackValue;
        private const double m_MaxScaleValue = 0.3;// Set the maximum scale value possible for the zoom out (hardcoded for now...)

        internal ZoomingState(Con10uumGestureStateManager con10uumMultitouchStateMachine)
        {
            m_Con10uumGestureStateManager = con10uumMultitouchStateMachine;
            m_Con10uum = m_Con10uumGestureStateManager.Con10uum;
            m_Con10uum.Loaded += new RoutedEventHandler(m_Con10uum_Loaded); ;
        }

        void m_Con10uum_Loaded(object sender, RoutedEventArgs e)
        {
            m_Con10uumPanel = (Con10uumPanel)m_Con10uum.Template.FindName("Con10uumPanel", m_Con10uum);
        }

        void m_StoryboardScaleTo03_Completed(object sender, EventArgs e)
        {
            m_StoryboardScaleTo03.Stop();
            m_Con10uumPanel.ZoomScaleTransform.ScaleY = m_Con10uumPanel.ZoomScaleTransform.ScaleX = 0.3;
            m_Con10uumGestureStateManager.GotoZoomedState();
        }

        internal override void EnteringState()
        {
            m_ScaleGestureTrackValue = m_Con10uumPanel.ZoomScaleTransform.ScaleY;

            Point center = new Point(m_Con10uumPanel.ViewportWidth / 2, m_Con10uumPanel.ViewportHeight / 10);
            m_Con10uumPanel.ZoomScaleTransform.CenterX = center.X;
            m_Con10uumPanel.ZoomScaleTransform.CenterY = center.Y;
            m_StoryboardScaleTo03 = Con10uumPanel.GetStoryboardScaleTo03();
            m_StoryboardScaleTo03.Completed += new EventHandler(m_StoryboardScaleTo03_Completed);
            m_Con10uumPanel.Resources.Add("ZoomingState.m_StoryboardScaleTo03", m_StoryboardScaleTo03);
        }

        internal override void ExitingState()
        {
            m_StoryboardScaleTo03.Completed -= new EventHandler(m_StoryboardScaleTo03_Completed);
            m_Con10uumPanel.Resources.Remove("ZoomingState.m_StoryboardScaleTo03");
            m_StoryboardScaleTo03 = null;
        }

        internal override void ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {            
        }

        internal override void ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            //var touchcount = m_Con10uum.TouchesOver.Count();
            //if (touchcount > 3)
            //{
            var deltaManipulation = e.DeltaManipulation;
            
            //double maxScaleValue = 0.3; 
            double virtualMaxScaleValue = 0.6; //use to increase / decrease mouvement speed(hardcoded for now...)
            if (deltaManipulation.Scale.Y <= 1 && m_Con10uumPanel.ZoomScaleTransform.ScaleY > m_MaxScaleValue)
            {
                m_ScaleGestureTrackValue *= deltaManipulation.Scale.Y;

                //From scale = 1.0           : progress is 0
                //To   scale = maxScaleValue : progress is 1                    
                QuinticEase x = new QuinticEase();
                x.EasingMode = EasingMode.EaseIn;
                double progress = Math.Min((m_ScaleGestureTrackValue - 1) / (virtualMaxScaleValue - 1), 1.0);
                double alpha = x.Ease(progress);
                double easeScale = 1 + alpha * (m_MaxScaleValue - 1);

                //Reset ZoomScaleTransform Center
                Point center = new Point(m_Con10uumPanel.ViewportWidth / 2, m_Con10uumPanel.ViewportHeight / 10);
                m_Con10uumPanel.ZoomScaleTransform.CenterX = center.X;
                m_Con10uumPanel.ZoomScaleTransform.CenterY = center.Y;
                m_Con10uumPanel.ZoomScaleTransform.ScaleY = m_Con10uumPanel.ZoomScaleTransform.ScaleX = easeScale;
                m_Con10uumPanel.InvalidateArrange();
                //m_Con10uumPanel.ScrollOwner.InvalidateScrollInfo();
            }
        
            e.Handled = true;
            //}
        }

        internal override void ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            if (DoubleUtil.AreClose(m_Con10uumPanel.ZoomScaleTransform.ScaleX, m_MaxScaleValue))
            {
                m_Con10uumGestureStateManager.GotoZoomedState();
            }
            else
            {
                //force ZoomedState  (or return to normal state?)
                try
                {
                    m_StoryboardScaleTo03.Begin();
                }
                catch(Exception exc)
                {
                }
            }
        }

        internal override void ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
        {
        }
    }

    internal class ZoomedState : Con10uumStateBase
    {
        private Con10uumGestureStateManager m_Con10uumGestureStateManager;
        private Con10uum m_Con10uum;
        private Con10uumPanel m_Con10uumPanel;
        private Storyboard m_StoryboardScaleTo1;
        private double m_Con10uumPanelHorizontalOffsetBackup;
        private DispatcherTimer m_CloseDispatcherTimer = new DispatcherTimer();

        internal ZoomedState(Con10uumGestureStateManager con10uumMultitouchStateMachine)
        {
            m_Con10uumGestureStateManager = con10uumMultitouchStateMachine;
            m_Con10uum = m_Con10uumGestureStateManager.Con10uum;
            m_Con10uum.Loaded += new RoutedEventHandler(m_Con10uum_Loaded);
            
            m_CloseDispatcherTimer.Interval = TimeSpan.FromSeconds(4);
            m_CloseDispatcherTimer.Tick += new EventHandler(m_CloseDispatcherTimer_Tick);
        }

        void m_CloseDispatcherTimer_Tick(object sender, EventArgs e)
        {
            m_StoryboardScaleTo1.Begin();
            m_CloseDispatcherTimer.Stop();
        }

        void m_Con10uum_Loaded(object sender, RoutedEventArgs e)
        {
            m_Con10uumPanel = (Con10uumPanel)m_Con10uum.Template.FindName("Con10uumPanel", m_Con10uum);

            m_StoryboardScaleTo1 = Con10uumPanel.GetStoryboardScaleTo1();
            //m_StoryboardScaleTo1 = (Storyboard)m_Con10uumPanel.FindResource("StoryboardScaleTo1");
            m_StoryboardScaleTo1.Completed += new EventHandler(m_StoryboardScaleTo1_Completed);
        }

        void m_StoryboardScaleTo1_Completed(object sender, EventArgs e)
        {
            m_StoryboardScaleTo1.Stop();
            m_Con10uumPanel.ZoomScaleTransform.ScaleY = m_Con10uumPanel.ZoomScaleTransform.ScaleX = 1.0;
            m_Con10uumGestureStateManager.GotoNormalState();
        }

        internal override void EnteringState()
        {
            m_CloseDispatcherTimer.Start();

            Point center = new Point(m_Con10uumPanel.ViewportWidth / 2, m_Con10uumPanel.ViewportHeight / 10);
            m_Con10uumPanel.ZoomScaleTransform.CenterX = center.X;
            m_Con10uumPanel.ZoomScaleTransform.CenterY = center.Y;
            m_StoryboardScaleTo1 = Con10uumPanel.GetStoryboardScaleTo1();
            m_StoryboardScaleTo1.Completed += new EventHandler(m_StoryboardScaleTo1_Completed);
            m_Con10uumPanel.Resources.Add("ZoomedState.m_StoryboardScaleTo1", m_StoryboardScaleTo1);
        }

        internal override void ExitingState()
        {
            m_CloseDispatcherTimer.Stop();
            m_StoryboardScaleTo1.Completed -= new EventHandler(m_StoryboardScaleTo1_Completed);
            m_Con10uumPanel.Resources.Remove("ZoomedState.m_StoryboardScaleTo1");
            m_StoryboardScaleTo1 = null;
        }

        internal override void ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            e.ManipulationContainer = m_Con10uum;
            e.Mode = ManipulationModes.All;
            e.Handled = true;
            m_Con10uumPanelHorizontalOffsetBackup = m_Con10uumPanel.HorizontalOffset;
            m_CloseDispatcherTimer.Stop();
        }

        internal override void ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            m_CloseDispatcherTimer.Stop();
            m_CloseDispatcherTimer.Start();
            var touchcount = m_Con10uum.TouchesOver.Count();
            if (touchcount > 0)
            {
                var deltaManipulation = e.DeltaManipulation;
                var cumulativeManipulation = e.CumulativeManipulation;
                
                //scroll gesture
                m_Con10uumPanel.SetHorizontalOffset(m_Con10uumPanel.HorizontalOffset - (deltaManipulation.Translation.X * (1 / m_Con10uumPanel.ZoomScaleTransform.ScaleX)));

//                m_Con10uumPanel.SetHorizontalOffset(m_Con10uumPanel.HorizontalOffset - ApplyZoomToScroll(deltaManipulation.Translation.X));
                //Debug.WriteLine(ApplyZoomToScroll(deltaManipulation.Translation.X));
                e.Handled = true;
            }
        }

        private double ApplyZoomToScroll(double horizontalOffset)
        {
            double scrollRatio = horizontalOffset / m_Con10uumPanel.ExtentWidth;
            return scrollRatio * (m_Con10uumPanel.ExtentWidth * m_Con10uumPanel.ZoomScaleTransform.ScaleX);
        }

        internal override void ManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
        {
            m_Con10uumPanelHorizontalOffsetBackup = 0;
            m_CloseDispatcherTimer.Start();           
        }

        internal override void ManipulationBoundaryFeedback(object sender, ManipulationBoundaryFeedbackEventArgs e)
        {
        }

        internal void ReportCon10uumWindow_Tap(object sender, RoutedEventArgs e)
        {
            m_CloseDispatcherTimer.Stop();
            m_StoryboardScaleTo1.Begin();
            m_Con10uumPanel.MakeVisible((UIElement)sender, new Rect(((UIElement)sender).RenderSize));
        }
    }

    internal static class DoubleUtil
    {
        internal const double DBL_EPSILON = 2.2204460492503131E-16;
        internal const float FLT_MIN = 1.175494E-38f;

        public static bool AreClose(double value1, double value2)
        {
            if (value1 == value2)
            {
                return true;
            }
            double num2 = ((Math.Abs(value1) + Math.Abs(value2)) + 10.0) * 2.2204460492503131E-16;
            double num = value1 - value2;
            return ((-num2 < num) && (num2 > num));
        }

        public static bool AreClose(Point point1, Point point2)
        {
            return (AreClose(point1.X, point2.X) && AreClose(point1.Y, point2.Y));
        }

        public static bool AreClose(Rect rect1, Rect rect2)
        {
            if (rect1.IsEmpty)
            {
                return rect2.IsEmpty;
            }
            return (((!rect2.IsEmpty && AreClose(rect1.X, rect2.X)) && (AreClose(rect1.Y, rect2.Y) && AreClose(rect1.Height, rect2.Height))) && AreClose(rect1.Width, rect2.Width));
        }

        public static bool AreClose(Size size1, Size size2)
        {
            return (AreClose(size1.Width, size2.Width) && AreClose(size1.Height, size2.Height));
        }

        public static bool AreClose(Vector vector1, Vector vector2)
        {
            return (AreClose(vector1.X, vector2.X) && AreClose(vector1.Y, vector2.Y));
        }

        public static int DoubleToInt(double val)
        {
            if (0.0 >= val)
            {
                return (int)(val - 0.5);
            }
            return (int)(val + 0.5);
        }

        public static bool GreaterThan(double value1, double value2)
        {
            return ((value1 > value2) && !AreClose(value1, value2));
        }

        public static bool GreaterThanOrClose(double value1, double value2)
        {
            if (value1 <= value2)
            {
                return AreClose(value1, value2);
            }
            return true;
        }

        public static bool IsBetweenZeroAndOne(double val)
        {
            return (GreaterThanOrClose(val, 0.0) && LessThanOrClose(val, 1.0));
        }

        public static bool IsNaN(double value)
        {
            NanUnion union = new NanUnion();
            union.DoubleValue = value;
            ulong num = union.UintValue & 0xfff0000000000000L;
            ulong num2 = union.UintValue & ((ulong)0xfffffffffffffL);
            if ((num != 0x7ff0000000000000L) && (num != 0xfff0000000000000L))
            {
                return false;
            }
            return (num2 != 0x0L);
        }

        public static bool IsOne(double value)
        {
            return (Math.Abs((double)(value - 1.0)) < 2.2204460492503131E-15);
        }

        public static bool IsZero(double value)
        {
            return (Math.Abs(value) < 2.2204460492503131E-15);
        }

        public static bool LessThan(double value1, double value2)
        {
            return ((value1 < value2) && !AreClose(value1, value2));
        }

        public static bool LessThanOrClose(double value1, double value2)
        {
            if (value1 >= value2)
            {
                return AreClose(value1, value2);
            }
            return true;
        }

        public static bool RectHasNaN(Rect r)
        {
            if ((!IsNaN(r.X) && !IsNaN(r.Y)) && (!IsNaN(r.Height) && !IsNaN(r.Width)))
            {
                return false;
            }
            return true;
        }

        [StructLayout(LayoutKind.Explicit)]
        private struct NanUnion
        {
            [FieldOffset(0x0)]
            internal double DoubleValue;
            [FieldOffset(0x0)]
            internal ulong UintValue;
        }
    }

}
